﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Collections.Concurrent;
using System.Threading;

namespace Server
{
    class Program
    {
        private const int DATA_RECEIVER_PORT = 10204;

        private static List<ClientInfo> clients = new List<ClientInfo>();
        private static byte currentId = 1;

        private static BlockingCollection<byte[]> queue = new BlockingCollection<byte[]>(new ConcurrentQueue<byte[]>());

        private static UdpClient dataManager = new UdpClient(DATA_RECEIVER_PORT);

        static void Main(string[] args)
        {
            const int SERVER_PORT = 10203;

            using (UdpClient server = new UdpClient(SERVER_PORT))
            {
                Thread receiver = new Thread(ReceiveDrawingData);
                Thread sender = new Thread(SendDrawingData);

                receiver.Start();
                sender.Start();

                Console.WriteLine(String.Format("Server available at {0}", SERVER_PORT));

                while (true)
                {
                    IPEndPoint clientEndpoint = null;
                    byte[] datagram = server.Receive(ref clientEndpoint);

                    String message = Encoding.UTF8.GetString(datagram);

                    if (message == "CONNECT")
                    {
                        clients.Add(new ClientInfo(currentId, clientEndpoint));
                        currentId++;

                        Console.WriteLine(String.Format("{0}:{1} is connected", clientEndpoint.Address, clientEndpoint.Port));

                        int[] data = { DATA_RECEIVER_PORT };
                        
                        byte[] dataToSend = new byte[sizeof(int)];
                     
                        Buffer.BlockCopy(data, 0, dataToSend, 0, dataToSend.Length);

                        server.Send(dataToSend, dataToSend.Length, clientEndpoint);
                    }
                    else if (message == "DISCONNECT")
                    {
                        ClientInfo clientToRemove = clients.SingleOrDefault(c => c.Endpoint.Address.Equals(clientEndpoint.Address)
                            && c.Endpoint.Port == clientEndpoint.Port);

                        clients.Remove(clientToRemove);

                        Console.WriteLine(String.Format("Client {0}:{1} disconnected", clientToRemove.Endpoint.Address,
                            clientToRemove.Endpoint.Port));
                    }
                }
            }
        }

        private static void ReceiveDrawingData()
        {
            while (true)
            {
                IPEndPoint clientEndpoint = null;
                List<byte> data = dataManager.Receive(ref clientEndpoint).ToList();

                ClientInfo clientInfo = clients.SingleOrDefault(c => c.Endpoint.Address.Equals(clientEndpoint.Address)
                    && c.Endpoint.Port == clientEndpoint.Port);

                byte clientId = 0;

                if (clientInfo != null)
                {
                    clientId = clientInfo.Id;
                }

                data.Add(clientId);

                queue.Add(data.ToArray());
            }
        }

        private static void SendDrawingData()
        {
            while (true)
            {
                byte[] drawingData = null;

                if (queue.TryTake(out drawingData))
                {
                    foreach (var client in clients)
                    {
                        dataManager.Send(drawingData, drawingData.Length, client.Endpoint);
                    }
                }
            }
        }
    }
}
